import java.util.Collection;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.*;


/* A simple symbol table for semantic analysis
 * 
 */
public class SymbolTable {
	// The Hash Table used to build the Symbol Table
	public Hashtable<String, Entry> st;
	
	public ArrayList<String> methods = new ArrayList<String>();
	public ArrayList<String> methodsReturn = new ArrayList<String>();
	// The level of the scope of the current instance
	private int level;
	
	// The current instance of the class
	private static SymbolTable inst;

	private static int numberOfIfs;
	private static int returnsOfIfs;

	public static int numberOfElses;
	public static int returnsOfElses;

	private static boolean inIf = false;
	public static boolean inElse = false;
	private static boolean unCondIf = false;
	
	/*
	 * The constructor is set to private
	 * to prevent the creation of more than
	 * one instance of the class. 
	 */
	private SymbolTable() {
		st = new Hashtable<String, Entry>();
		level = 0;
	}
	
	/* 
	 * This method is used to get the current instance
	 * of the SymbolTable.
	 */
	public static SymbolTable getInstance() {
		if(inst==null)
			inst = new SymbolTable();
		
		return inst;
	}

	public static boolean getUnCondIf() {
		return unCondIf;
	}

	public static void toggleUnCondIf() {
		unCondIf = !unCondIf;
	}

	public static int getNumOfIfs() {
		return numberOfIfs;
	}

	public static boolean getInIf() {
		return inIf;
	}

	public static void toggleInIf() {
		inIf = !inIf;
	}

	public static void toggleInElse() {
		inElse = !inElse;
	}

	public static int getNumOfReturns() {
		return returnsOfIfs;
	}

	public static void incrementNumOfIfs() {
		numberOfIfs += 1;
	}

	public static void incrementNumOfReturns() {
		returnsOfIfs += 1;
	}

	public static void resetIfCounters() {
		numberOfIfs = 0;
		returnsOfIfs = 0;
		numberOfElses = 0;
		returnsOfElses = 0;
	}
	
	public void addMethod(String m, String r) {
		methods.add(m);
		methodsReturn.add(r);
	}

	public boolean containsMethod(String m) {
		return methods.contains(m);
	}

	public int getIndexMethod(String m) {
		return methods.indexOf(m);
	}
	/*
	 * This method is used to add entries to the table
	 */
	public void add(Entry e) {
		e.level = level;
		st.put(e.id, e);
	}
	
	/*
	 * This method is used to get entries from the table
	 */
	public Entry get(String k) {
		return st.get(k);
	}
	
	/*
	 * This method checks if a certain identifier
	 * is already in the symbol table.
	 */
	public boolean contains(String id) {
		if(st.containsKey(id)){
			Entry e = get(id);
			if(e.level == level)
				return true;
		}
		return false;
	}
	
	/*
	 * This method opens a new scope in the hashtable
	 */
	public void openScope() {
		level++;
	}
	
	
	/*
	 * This methods closes the current scope and
	 * removes all the entries that were created
	 * created during that scope.
	 */
	public void closeScope() {
		
		Collection<Entry> ce = st.values();
		ArrayList<String> ls = new ArrayList<String>();
		
		for(Entry e: ce)
			if(e.level == level)
				ls.add(e.id);
				
		for(String s: ls)
			st.remove(s);
		
		level--;
	}

	public int getLevel() {
		return level;
	}

	public void print() {
		//System.out.println("Printing the entire symbol table.");
		Enumeration iterator = st.elements();
		while(iterator.hasMoreElements()) {
			Entry temp = (Entry)iterator.nextElement();
			if (temp.type != null) {
				//System.out.println("Lvl: " + temp.level + " " + temp.id + " " + temp.type.type);	
			} else {
				//System.out.println("Lvl: " + temp.level + " " + temp.id);
			}	
		}
	}

	public Entry getMethodEntry() {
		Enumeration iterator = st.elements();
		while(iterator.hasMoreElements()) {
			Entry temp = (Entry)iterator.nextElement();
			if (temp.isMethod == true) {
				return temp;
			} 
		}
		return new Entry("null", new Type("null", 0, 0));
	}
	
	public static void main(String[] args) {
		SymbolTable a = SymbolTable.getInstance();
		a.openScope();
		a.add(new Entry("X"));
		
		//System.out.println(a.contains("X"));
		
		//System.out.println(a.contains("Y"));
		//System.out.println(a.contains("X"));
		a.closeScope();
		
		//System.out.println(a.contains("X"));
	}
}
